﻿using System.Net.Sockets;
using System.Text;

namespace SimS7Study.Lib
{
    /// <summary>
    /// 西门子S7通信库封装
    /// </summary>
    public class S7Lib
    {
        private Socket socket;

        /// <summary>
        /// 是否已经连接
        /// </summary>

        private bool _Connected;

        /// <summary>
        /// 是否已经连接
        /// </summary>
        public bool Connected
        {
            get { return _Connected; }
        }


        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="ip">plc的ip</param>
        /// <param name="rack">目标机架号</param>
        /// <param name="slot">目标槽号</param>
        public void Connect(string ip, ushort rack, ushort slot)
        {
            socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            #region 三次握手
            socket.Connect(ip, 102);
            #endregion

            #region COTP连接

            byte[] bytes = [
                 #region TPKT
                0x03,//版本号
                0x00,//保留

                0x00,//长度
                0x16,
                  #endregion

                #region COTP
                0x11,//该字节之后的所有字节数
                0xe0,//PDU类型   附录一

                0x00,//目标引用
                0x00,

                0x00,//源引用
                0x01,

                0x00,//扩展格式

                0xc1,
                0x02,// 原tsap长度
                0x01,// 源连接模式。附录二
                0x00,// 源连接模式下的【机架号和槽号】写0x00即可,在大多数情况下，作为客户端(上位机)的应用程序并不需要关心自己的机架号和槽号。源连接模式：由于客户端本身不需要标识其硬件位置，因此机架号和槽号通常设为0,0。

                0xc2,
                0x02,
                0x03, //目标连接模式
               (byte)( rack*32+slot), //机架号左移5位加上槽号  这里是PLC的机架号和槽号。这意味着你要连接的是第0个机架上的第1个插槽中的CPU模块。目标连接模式：为了准确找到并连接到特定的PLC模块，必须提供正确的机架号和槽号，如S7-1200/S7-1500常见的0,1配置。

                0xc0,
                0x01,
                0x0a//2的10次方
	            #endregion
            ];

            SendReceive(bytes);//发送并接收
            #endregion

            #region setup communication的报文
            bytes = [
                #region TPKT
                 0x03,//版本号
                0x00,//保留

                0x00,//长度
                0x19,
                #endregion

                #region COTP
                    0x02,//当前cotp后面的字节数
                    0xf0,//pdu类型
                    0x80,//固定
                #endregion

                #region S7 Communication-header
                    0x32,//协议ID 固定

                    0x01,//远程操作命令 附录三

                    0x00,//预留
                    0x00,

                    0xff,// 协议单元事件引用 累加
                    0xff,

                    0x00,//参数长度
                    0x08,

                    0x00,//数据长度
                    0x00,
                #endregion

                #region  S7 Communication-parameter
                    0xf0,//功能码。通信功能是f0。附录四
                    0x00,//预留

                    0x00,//amq(calling)
                    0x03,

                    0x00,//amq(called)
                    0x03,

                    0x03,//pdu长度
                    0xc0
               #endregion
            ];
            #endregion

            byte[] receiveBytes = new byte[27];

            byte[] resultBytes = SendReceive(bytes);
            //连接异常检查
            ushort headerErrorCode = BitConverter.ToUInt16([resultBytes[14], resultBytes[13]]);
            if (headerErrorCode != 0)
            {
                //throw new  Exception("连接异常");
                throw new Exception(ErrorDictionary.HeaderErrorDictionary[headerErrorCode]);
            }
            _Connected = true;

        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if (socket != null && _Connected)
            {
                socket.Close();
                _Connected = false;
            }
        }

        /// <summary>
        /// 读一项
        /// </summary>
        /// <param name="dataParameter">读取数据信息</param>
        /// <returns>数据byte数组</returns>
        public byte[] ReadSingle(ReadDataParameter dataParameter)
        {
            byte[] bytesReadCount = BitConverter.GetBytes(dataParameter.ReadCount);//读取个数
            byte[] bytesDbNum = BitConverter.GetBytes(dataParameter.DBNumber);//模块编号
            byte[] bytesAddr = BitConverter.GetBytes(dataParameter.ByteAddress * 8 + dataParameter.BitAddress);//起始地址

            byte[] readBytes = [
                #region TPKT
                   0x03,//版本号
                    0x00,//预留

                    0x00,//整个报文长度  以后需要计算
                    0x1f,
                #endregion

                #region COTP
                    0x02,
                    0xf0,
                    0x80,
                #endregion

                #region S7 communication-header
                    0x32,
                    0x01,

                    0x00,
                    0x00,
                    0x00,
                    0x00,

                    0x00,
                    0x0e,//parameter的长度。

                    0x00,
                    0x00,
                #endregion

                #region S7 communication-Parameter
                    0x04,//读变量04。附录四
                    0x01,//读几项

                #region Item1的参数信息
                    0x12,//固定值
                    0x0a,//item1之后的字节数
                    0x10,//固定
               
                    (byte)dataParameter.ParameterType,//parameter数据类型。附录八

                    bytesReadCount[1],//读取个数
                    bytesReadCount[0],

                    bytesDbNum[1],//db编号
                    bytesDbNum[0],

                    (byte)dataParameter.Area,//存储区

                 //读的起始地址(从哪个字节开始+哪个位开始)
               
                 bytesAddr[2],
                 bytesAddr[1],
                 bytesAddr[0],

                 #endregion

                #endregion
            ];

            //返回的字节，不包括tpkt的4个字节
            byte[] resultBytes = SendReceive(readBytes);

            //读异常检查
            ushort headerErrorCode = BitConverter.ToUInt16([resultBytes[14], resultBytes[13]]);
            if (headerErrorCode != 0)
            {
                //throw new  Exception("读异常");
                throw new Exception(ErrorDictionary.HeaderErrorDictionary[headerErrorCode]);
            }

            //data异常(返回Data的item项中都有一个结果码)
            if (resultBytes[17] != 0xff)
            {
                throw new Exception(ErrorDictionary.DataErrorDictionary[resultBytes[17]]);
            }

            //先获取数据长度 有可能是位的长度
            ushort dataLen = BitConverter.ToUInt16([resultBytes[20], resultBytes[19]]);

            if (resultBytes[18] != 0x03 && resultBytes[18] != 0x07)//bit和real返回的都是字节长度，其他返回的都是位长度
            {
                dataLen = (ushort)(dataLen / 8);
            }

            return resultBytes.ToList().GetRange(21, dataLen).ToArray();
        }


        /// <summary>
        /// 读多项
        /// </summary>
        /// <param name="dataParameters">读取数据信息</param>
        /// <returns>数据byte数组</returns>
        public void ReadMulti(params ReadDataParameter[] dataParameters)
        {
            //总报文长度=19个固定字节+item(每个item固定为12字节)*项数(dataParameters.Length)
            short totalLen = (short)(19 + 12 * dataParameters.Length);
            byte[] totalLenBytes = BitConverter.GetBytes(totalLen);

            // 参数长度=固定2个字节(功能码,通信项数) + item(每个item固定为12字节) *  项数(dataParameters.Length)
            // 还可以:参数长度 = 总报文长度 - 19
            short parameterLen = (short)(2 + 12 * dataParameters.Length);
            byte[] parameterLenBytes = BitConverter.GetBytes(parameterLen);

            List<byte> readBytes = [
                #region TPKT
                   0x03,//版本号
                    0x00,//预留

                   totalLenBytes[1],//整个报文长度  需要计算
                   totalLenBytes[0],
                #endregion

                #region COTP
                    0x02,
                    0xf0,
                    0x80,
                #endregion

                #region S7 communication-header
                    0x32,
                    0x01,

                    0x00,
                    0x00,
                    0x00,
                    0x00,

                    parameterLenBytes[1],//parameter的长度 需要计算。
                    parameterLenBytes[0],

                    0x00,   // 数据长度不用管，读固定是0x00
                    0x00,
                #endregion

                #region S7 communication-Parameter
                    0x04,//读变量04。附录四
                    (byte)dataParameters.Length,//读几项
                #endregion
            ];

            //向报文add每个Item的报文
            foreach (ReadDataParameter item in dataParameters)
            {
                byte[] bytesReadCount = BitConverter.GetBytes(item.ReadCount);//读取个数
                byte[] bytesDbNum = BitConverter.GetBytes(item.DBNumber);//模块编号
                byte[] bytesAddr = BitConverter.GetBytes(item.ByteAddress * 8 + item.BitAddress);//起始地址

                byte[] itemBytes =
                    [
                        0x12,//固定值
                        0x0a,//item之后的字节数
                        0x10,//固定

                        (byte)item.ParameterType,//parameter数据类型。附录八

                        bytesReadCount[1],//读取个数
                        bytesReadCount[0],

                        bytesDbNum[1],//db编号
                        bytesDbNum[0],

                        (byte)item.Area,//存储区

                        //读的起始地址(从哪个字节开始+哪个位开始)
                        bytesAddr[2],
                        bytesAddr[1],
                        bytesAddr[0],
                    ];

                readBytes.AddRange(itemBytes);
            }

            //返回的字节，不包括tpkt的4个字节
            byte[] resultBytes = SendReceive(readBytes.ToArray());

            //读异常检查
            ushort headerErrorCode = BitConverter.ToUInt16([resultBytes[14], resultBytes[13]]);
            if (headerErrorCode != 0)
            {
                //throw new  Exception("读异常");
                throw new Exception(ErrorDictionary.HeaderErrorDictionary[headerErrorCode]);
            }

            //解析每一项
            int startIndex = 17;//偏移量(接收不包括tpkt的4个字节)
            for (int i = 0; i < dataParameters.Length; i++)
            {
                if (resultBytes[startIndex] != 0xff)//数据错误((每个返回Data的item项中都有一个结果码))
                {
                    throw new Exception(ErrorDictionary.DataErrorDictionary[resultBytes[startIndex]]);
                }

                //数据长度(位、字节数)
                ushort dataLen = BitConverter.ToUInt16([resultBytes[startIndex + 3], resultBytes[startIndex + 2]]);
                if (resultBytes[startIndex + 1] != 0x03 && resultBytes[startIndex + 1] != 0x07)//bit和real返回的都是字节长度，其他返回的都是位长度
                {
                    dataLen = (ushort)(dataLen / 8);
                }

                //取出字节个数
                dataParameters[i].ResultDataBytes = resultBytes.ToList().GetRange(startIndex + 4, dataLen).ToArray();

                //偏移量增加
                startIndex += 4 + dataLen;

                if (dataLen % 2 == 1 && i < dataParameters.Length - 1)//字节数为奇数并且不是最后一项，则有fill bytes填充字节
                {
                    startIndex += 1;  // 有填充字节偏移量要+1
                }
            }
        }

        /// <summary>
        /// 写short
        /// </summary>
        /// <param name="dbNum"></param>
        /// <param name="byteAddr"></param>
        /// <param name="bitAddr"></param>
        /// <param name="writeVal"></param>

        public void WriteShort(ushort dbNum, uint byteAddr, byte bitAddr, short writeVal)
        {
            List<byte> valList = BitConverter.GetBytes(writeVal).ToList();
            valList.Reverse();
            WriteDataParameter writeDataParameter = new WriteDataParameter
            {

                Area = Area.DBAndV,
                ParameterType = ParameterType.INT,  // PLC int->c# short
                WriteCount = 1,
                ByteAddress = byteAddr,
                BitAddress = bitAddr,
                DataType = DataType.INT,
                DBNumber = dbNum,
                WriteBytes = valList.ToArray()
            };

            WriteSingle(writeDataParameter);
        }

        /// <summary>
        /// 写ushort
        /// </summary>
        /// <param name="dbNum"></param>
        /// <param name="byteAddr"></param>
        /// <param name="bitAddr"></param>
        /// <param name="writeVal"></param>

        public void WriteUShort(ushort dbNum, uint byteAddr, byte bitAddr, ushort writeVal)
        {
            List<byte> valList = BitConverter.GetBytes(writeVal).ToList();
            valList.Reverse();
            WriteDataParameter writeDataParameter = new WriteDataParameter
            {
                Area = Area.DBAndV,
                ParameterType = ParameterType.WORD, // PLC word -> c# ushort
                WriteCount = 1,
                ByteAddress = byteAddr,
                BitAddress = bitAddr,
                DataType = DataType.WORD,
                DBNumber = dbNum,
                WriteBytes = valList.ToArray()
            };

            WriteSingle(writeDataParameter);
        }

        /// <summary>
        /// 写float
        /// </summary>
        /// <param name="dbNum"></param>
        /// <param name="byteAddr"></param>
        /// <param name="bitAddr"></param>
        /// <param name="writeVal"></param>

        public void WriteFloat(ushort dbNum, uint byteAddr, byte bitAddr, float writeVal)
        {
            List<byte> valList = BitConverter.GetBytes(writeVal).ToList();
            valList.Reverse();
            WriteDataParameter writeDataParameter = new WriteDataParameter
            {

                Area = Area.DBAndV,
                ParameterType = ParameterType.REAL, // PLC real -> float
                WriteCount = 1,
                ByteAddress = byteAddr,
                BitAddress = bitAddr,
                DataType = DataType.REAL,
                DBNumber = dbNum,
                WriteBytes = valList.ToArray()
            };

            WriteSingle(writeDataParameter);
        }

        /// <summary>
        /// 写int
        /// </summary>
        /// <param name="dbNum"></param>
        /// <param name="byteAddr"></param>
        /// <param name="bitAddr"></param>
        /// <param name="writeVal"></param>

        public void WriteInt(ushort dbNum, uint byteAddr, byte bitAddr, int writeVal)
        {
            List<byte> valList = BitConverter.GetBytes(writeVal).ToList();
            valList.Reverse();
            WriteDataParameter writeDataParameter = new WriteDataParameter
            {

                Area = Area.DBAndV,
                ParameterType = ParameterType.BYTE,
                WriteCount = 4,
                ByteAddress = byteAddr,
                BitAddress = bitAddr,
                DataType = DataType.BYTE,
                DBNumber = dbNum,
                WriteBytes = valList.ToArray()
            };

            WriteSingle(writeDataParameter);
        }

        /// <summary>
        /// 向Q区写bool
        /// </summary>
        /// <param name="area">区域</param>
        /// <param name="byteAddr"></param>
        /// <param name="bitAddr"></param>
        /// <param name="writeVal"></param>

        public void WriteBool(Area area,uint byteAddr, byte bitAddr, bool writeVal)
        {
            WriteDataParameter writeDataParameter = new WriteDataParameter
            {
                Area = area,
                ParameterType = ParameterType.BIT,
                WriteCount = 1,
                ByteAddress = byteAddr,
                BitAddress = bitAddr,
                DataType = DataType.BIT,
                DBNumber = 0,
                WriteBytes = writeVal ? [0x01] : [0x00]
            };

            WriteSingle(writeDataParameter);
        }

        /// <summary>
        /// 写字符串
        /// </summary>
        /// <param name="dbNum">DB块编号</param>
        /// <param name="plcStrLen">PLC定义的字符串长度</param>
        /// <param name="byteAddr">起始字节位置</param>
        /// <param name="bitAddr">第几位</param>
        /// <param name="str">字符串</param>
        public void WriteString(ushort dbNum,byte plcStrLen,uint byteAddr, byte bitAddr, string str)
        {
            byte[] strBytes = Encoding.ASCII.GetBytes(str);
            List<byte> totalBytes = [plcStrLen, (byte)strBytes.Length];
            totalBytes.AddRange(strBytes);

            WriteDataParameter writeDataParameter = new WriteDataParameter
            {
                Area = Area.DBAndV,
                ParameterType = ParameterType.BYTE,
                WriteCount = (short)totalBytes.Count,  // 这里是实际写的字节个数,string[10],最多能写12个字节
                ByteAddress = byteAddr,
                BitAddress = bitAddr,
                DataType = DataType.BYTE,
                DBNumber = dbNum,
                WriteBytes = totalBytes.ToArray()
            };

            WriteSingle(writeDataParameter);
        }

        /// <summary>
        /// 写单项
        /// </summary>
        /// <param name="writeDataParameter"></param>
        public void WriteSingle(WriteDataParameter writeDataParameter)
        {

            #region 基本校验
            ushort computerByteCount = (ushort)writeDataParameter.WriteBytes.Length;//计算出来的字节数 要 与写进去的字节数一样
            switch (writeDataParameter.ParameterType)
            {
                case ParameterType.INT:
                case ParameterType.WORD:
                    computerByteCount = (ushort)(writeDataParameter.WriteCount * 2);
                    break;

                case ParameterType.DWORD:
                case ParameterType.REAL:
                case ParameterType.DINT:
                    computerByteCount = (ushort)(writeDataParameter.WriteCount * 4);
                    break;

                case ParameterType.BIT:
                    computerByteCount = 1;//只能写1个bit (写入的是bit,只能写1个bit,不能有多个,写入bit是写不了多个位的,所以强制为1)
                    break;

                default:
                    computerByteCount = (ushort)writeDataParameter.WriteBytes.Length;
                    break;
            }

            if (computerByteCount != (ushort)writeDataParameter.WriteBytes.Length)
            {
                throw new Exception("数据错误");
            }

            #endregion

            // 报文总长度=19(固定长度tpkt+cotp+header+parameter的前2个字节)+12(parameter的item项)*1(parameter写入的项数)+4(data中的item项的固定长度前面4个字节)*1(data写入的项数)+写入的字节长度
            short totalLen = (short)(19 + 12 * 1 + 4 * 1 + writeDataParameter.WriteBytes.Length);//报文总长度
            byte[] totalLenBytes = BitConverter.GetBytes(totalLen);

            //data的长度
            short dataLen = (short)(4 + writeDataParameter.WriteBytes.Length);
            byte[] dataLenBytes = BitConverter.GetBytes(dataLen);

            //写的个数
            byte[] writeCountBytes = BitConverter.GetBytes(writeDataParameter.WriteCount);

            //db模块编号
            byte[] dbNumBytes = BitConverter.GetBytes(writeDataParameter.DBNumber);


            //起始地址
            byte[] bytesAddr = BitConverter.GetBytes(writeDataParameter.ByteAddress * 8 + writeDataParameter.BitAddress);//起始地址

            //data 里面写的数据长度，可能是字节数/位数
            short dataWriteCount = (short)writeDataParameter.WriteBytes.Length;
            //如果不是bit和real，那么个数就是位数
            if (writeDataParameter.DataType != DataType.BIT && writeDataParameter.DataType != DataType.REAL)
            {
                dataWriteCount *= 8;
            }
            byte[] dataWriteCountBytes = BitConverter.GetBytes(dataWriteCount);

            List<byte> writeBytes = [
                #region TPKT
                0x03,//0
                0x00,//1\
              
                totalLenBytes[1],//报文总长度
                totalLenBytes[0],
                #endregion

                #region COTP
                0x02,//4
                0xf0,//5
                0x80,//6
                #endregion

                #region S7-Header
                    0x32,//7
                    0x01,//8

                    0x00,//9
                    0x00,//10

                    0x00,//11
                    0x03,//12

                    0x00,//13  参数长度
                    0x0e,//14  2+12*1(参数项数) --> (2 + 12 * 参数项数)
                   
                    dataLenBytes[1],//数据长度  (data中的字节长度)
                    dataLenBytes[0],
                #endregion

                #region S7-Parameter
                         0x05,//17 功能码 05表示write
                         0x01,//18 写多少项(parameter的项数代表通信的项数)
                        #region Item1的参数
                         0x12,//19 固定
                         0x0a,//20 item1之后的字节数
                         0x10,//21 固定
                         (byte)writeDataParameter.ParameterType,//参数数据类型
                       
                         writeCountBytes[1], // 写的个数
                         writeCountBytes[0],

                         dbNumBytes[1],  // db模块编号
                         dbNumBytes[0],

                         (byte)writeDataParameter.Area,//27 存储区  比如0x84表示db

                         //写数据的起始地址
                         //0000 0000  0000 0000 0001 0000
                         bytesAddr[2],//28
                         bytesAddr[1],//29
                         bytesAddr[0],//30
                    #endregion
                #endregion

                #region S7-data
                    #region Item1的数据
                         0x00,//31 固定
                         (byte)writeDataParameter.DataType,//32 byte/word/dword 都是04
                       
                         dataWriteCountBytes[1],
                         dataWriteCountBytes[0],

                         //数据数组
                         //0x00,//35
                         //0x00,//36
                         //0x00,//37 
                         //0xea,//38
                 #endregion
                #endregion
            ];

            writeBytes.AddRange(writeDataParameter.WriteBytes);

            byte[] resultBytes = SendReceive(writeBytes.ToArray());

            //读异常检查
            ushort headerErrorCode = BitConverter.ToUInt16([resultBytes[14], resultBytes[13]]);
            if (headerErrorCode != 0)
            {
                //throw new  Exception("读异常");
                throw new Exception(ErrorDictionary.HeaderErrorDictionary[headerErrorCode]);
            }

            if (resultBytes[17] != 0xff)
            {
                throw new Exception(ErrorDictionary.DataErrorDictionary[resultBytes[17]]);
            }
        }

        /// <summary>
        /// 写多项
        /// </summary>
        /// <param name="writeDataParameters"></param>
        public void WriteMulti(params WriteDataParameter[] writeDataParameters)
        {
            //parametyer的byte[],组装写多项参数数组:BuildWriteParameterBytes
            byte[] parameterBytes = BuildWriteParameterBytes(writeDataParameters);
            // 组装数据的数组 BuildWriteDataBytes
            byte[] dataBytes = BuildWriteDataBytes(writeDataParameters);

            #region 计算各种长度

            //总长度
            ushort totalLen = (ushort)(19 + parameterBytes.Length + dataBytes.Length);
            byte[] totalLenBytes = BitConverter.GetBytes(totalLen);

            //参数长度
            ushort parameterLen = (ushort)(parameterBytes.Length + 2);
            byte[] parameterLenBytes = BitConverter.GetBytes(parameterLen);

            //数据字节总长度
            byte[] dataItemLenBytes = BitConverter.GetBytes((ushort)dataBytes.Length);
            #endregion

            List<byte> writeBytes = [
                  #region TPKT
                0x03,//0
                0x00,//1
              
                totalLenBytes[1],//报文总长度
                totalLenBytes[0],
                #endregion

                 #region COTP
                0x02,//4
                0xf0,//5
                0x80,//6
                #endregion
                
                #region S7-Header
                    0x32,//7
                    0x01,//8

                    0x00,//9
                    0x00,//10

                    0x00,//11
                    0x03,//12

                    parameterLenBytes[1],
                    parameterLenBytes[0],

                    dataItemLenBytes[1],//数据长度
                    dataItemLenBytes[0],
                #endregion

                #region S7-Parameter
                    0x05,//17 功能码 05表示write
                    (byte)writeDataParameters.Length,//18 写多少项(通信项数)
                #endregion
            ];

            //追加parameter
            writeBytes.AddRange(parameterBytes);
            //追加data
            writeBytes.AddRange(dataBytes);

            SendReceive(writeBytes.ToArray());
        }


        /// <summary>
        /// 组装写多项参数数组
        /// </summary>
        /// <param name="writeDataParameters"></param>
        /// <returns></returns>
        private byte[] BuildWriteParameterBytes(params WriteDataParameter[] writeDataParameters)
        {
            List<byte> resultBytes = new List<byte>();//结果byte集合
            foreach (WriteDataParameter item in writeDataParameters)
            {
                resultBytes.Add(0x12);
                resultBytes.Add(0x0a);
                resultBytes.Add(0x10);
                resultBytes.Add((byte)item.ParameterType);

                //写的数据个数
                byte[] writeCountBytes = BitConverter.GetBytes(item.WriteCount);
                resultBytes.Add(writeCountBytes[1]);
                resultBytes.Add(writeCountBytes[0]);

                //模块编号
                byte[] dbNumBytes = BitConverter.GetBytes(item.DBNumber);
                resultBytes.Add(dbNumBytes[1]);
                resultBytes.Add(dbNumBytes[0]);

                resultBytes.Add((byte)item.Area);

                //起始地址
                byte[] bytesAddr = BitConverter.GetBytes(item.ByteAddress * 8 + item.BitAddress);//起始地址
                resultBytes.Add(bytesAddr[2]);
                resultBytes.Add(bytesAddr[1]);
                resultBytes.Add(bytesAddr[0]);
            }

            return resultBytes.ToArray();
        }

        /// <summary>
        /// 组装写多项data数组
        /// </summary>
        /// <param name="writeDataParameters"></param>
        /// <returns></returns>
        private byte[] BuildWriteDataBytes(params WriteDataParameter[] writeDataParameters)
        {
            List<byte> resultBytes = new List<byte>();//结果byte集合
            for (int i = 0; i < writeDataParameters.Length; i++)
            {
                WriteDataParameter item = writeDataParameters[i];
                resultBytes.Add(0x00);
                resultBytes.Add((byte)item.DataType);

                //写的数据长度(位或字节数)
                short writeDataCount = (short)item.WriteBytes.Length;
                if (item.DataType != DataType.BIT && item.DataType != DataType.REAL)
                {
                    writeDataCount *= 8;
                }
                byte[] writeDataCountBytes = BitConverter.GetBytes(writeDataCount);
                resultBytes.Add(writeDataCountBytes[1]);
                resultBytes.Add(writeDataCountBytes[0]);

                //写的数据
                resultBytes.AddRange(item.WriteBytes);

                //添加填充字节 字节个数位奇数且不是最后一项
                if (item.WriteBytes.Length % 2 == 1 && i < writeDataParameters.Length - 1)
                {
                    resultBytes.Add(0x00);
                }
            }

            return resultBytes.ToArray();
        }


        /// <summary>
        /// 发送和接收
        /// </summary>
        /// <param name="sendBytes">发送报文</param>
        /// <returns>返回的报文，不包括tpkt这4个字节</returns>
        private byte[] SendReceive(byte[] sendBytes)
        {
            socket.Send(sendBytes);
            //1、先获取4个字节
            //2、再获取剩余字节(整个字节长度-4)
            byte[] tpktBytes = new byte[4];
            socket.Receive(tpktBytes);

            byte[] totalLenBytes = [tpktBytes[3], tpktBytes[2]];//tpkt的字节数组
            ushort totalLen = BitConverter.ToUInt16(totalLenBytes);//BitConverter小端

            byte[] otherBytes = new byte[totalLen - 4];//其他报文
            socket.Receive(otherBytes);
            return otherBytes;
        }
    }
}
